// OGoMojo
// Whitemice.MOGI.RPC2.FetchSpec
// License: LGPL
// Copyright: Whitemice Consulting, 2006
// Author(s): Adam Tauno Williams (awilliam@whitemice.org)
using System;
using System.Collections;
using CookComputing.XmlRpc;

namespace Whitemice.MOGI.RPC2 {
	/// \brief Fetch specification for querying the server
	/// \note The default of 25 for the object fetch limit is hardcoded
	/// \todo This doesn't seem to work for appointment.fetch
	public class FetchSpec {
		protected Hashtable		payload;
		protected Hashtable		fetchKeys;
		protected DocumentType	documentType;
		
		/// \brief Constructor
		public FetchSpec(IServer server, DocumentType documentType) {
			payload = new Hashtable();
			this.documentType = documentType;
			payload.Add("fetchLimit", "25");
			payload.Add("qualifier", "");
			FillFetchSpecMap(server);
		}
		
		/// \brief Constructor
		/// \param criteria
		/// \param max Maximum number of objects to return
		public FetchSpec(IServer server, DocumentType documentType, string[] criteria, int max) {
			this.documentType = documentType;
			payload = new Hashtable();
			payload.Add("fetchLimit", "25");
			payload.Add("qualifier", "");
			FillFetchSpecMap(server);
			this.BuildCriteria(criteria);
			this.Limit = max;
		}
		
		protected void FillFetchSpecMap(IServer server)
		{
			fetchKeys = new Hashtable();
			switch(documentType)
			{
				case DocumentType.Enterprise:
					fetchKeys.Add("name", "description");
					fetchKeys.Add("bankcode", "bankCode");
					fetchKeys.Add("bank", "bank");
					foreach(string xa in server.ExtendedAttributes(DocumentType.Enterprise))
						fetchKeys.Add(xa, xa);
					break;
				case DocumentType.Contact:
					fetchKeys.Add("firstname", "firstname");
					fetchKeys.Add("lastname", "name");
					fetchKeys.Add("initials", "initials");
					fetchKeys.Add("degree", "degree");
					fetchKeys.Add("middlename", "middlename");
					foreach(string xa in server.ExtendedAttributes(DocumentType.Contact))
						fetchKeys.Add(xa, xa);
					break;
				case DocumentType.Resource:
					fetchKeys.Add("description", "name");
					fetchKeys.Add("subject", "emailSubject");
					fetchKeys.Add("email", "email");
					fetchKeys.Add("category", "category");
					break;
				case DocumentType.Task:
					fetchKeys.Add("type", "type");
					fetchKeys.Add("title", "name");
					fetchKeys.Add("number", "number");
					fetchKeys.Add("status", "status");
					fetchKeys.Add("keywords", "keywords");
					break;
			}
		}		
		
		///  \brief Accessor for the attributes in the fetch specification
		public string[] Attributes {
			get {
				if (payload.Contains("hints"))
					return (string[])payload["hints"];
					else return null;
			}
			set {
				if (payload.Contains("hints"))
					payload["hints"] = value;
					else payload.Add("hints", value);
			}
		}
		
		/// \brief Accessor for the fetch limit
		public int Limit {
			get {
				return Convert.ToInt32((string)payload["fetchLimit"]);
			}
			set {
				payload["fetchLimit"] = value.ToString();
			}
		}
		
		/// \brief Accessor for the search qualifier
		public object Criteria {
			get {
				return payload["qualifier"];
			}
			set {
				BuildCriteria(((string)value).Split('~'));
			}
		} 
		
		/// \brief Accessor for the XmlRpcStruct created from the provided parameters
		/// \note Applications shouldn't use this method as it will tie the
		/// application to the XML-RPC implementation of MOGI
		public XmlRpcStruct Wire {
			get {
				XmlRpcStruct fetchSpec;
				
				Console.WriteLine("FSpec::Wire");				
				fetchSpec = new XmlRpcStruct();
				fetchSpec.Add("fetchLimit", this.Limit.ToString());
				
				if (this.Criteria is string)
					fetchSpec.Add("qualifier", (string)this.Criteria);
					else 
					{
						Console.WriteLine("Criteria is a dictionary");
						fetchSpec.Add("qualifier", (XmlRpcStruct)this.Criteria); 
					}
				return fetchSpec;
			}
		}
		
		/// \brief Construct a search criteria from a set of key pairs
		/// \param criteria
		/// \todo Provide a neat and tidy way to do search key substitution, probably higher level than here.
		public void BuildCriteria(string[] criteria) {
			
			payload["qualifier"] = "";
			switch(documentType)
			{
				case DocumentType.Task:
					BuildTaskCriteria(criteria);
					break;
				case DocumentType.Contact:
				case DocumentType.Enterprise:
					BuildCompanyCriteria(criteria);
					break;
				default:
					BuildOldCriteria(criteria);
					break;
			}
		}
		
		protected void BuildCompanyCriteria(string[] keypairs)
		{
			string qualifier;		
			
			Console.WriteLine("FetchSpec::BuildCompanyCriteria()");
			payload.Add("operation", "AND");
			Console.WriteLine("FetchSpec::BuildCompanyCriteria() - Pairs:{0}", keypairs.Length);
			if (keypairs.Length == 1)
			{
				string[] keypair;
				/// There is only one keypair
				keypair = keypairs[0].Split('=');
				if (keypair[1].IndexOf('*') > 0)
					/// keypair is a wildcard search
					qualifier = (string)fetchKeys[keypair[0]] + 
							" like \'" + keypair[1] + "\'";
					else
						/// keypair is an equality search
						qualifier = (string)fetchKeys[keypair[0]] +
							" = \'" + keypair[1] + "\'";				
			} else {
					/// There are multiple keypairs
					/// If there are multiple keypairs then the query 
					/// must be a wildcard search. Yes, this sucks.
					qualifier = "";
					foreach(string keypair in keypairs)
					{
						if(qualifier.Length > 0)
							qualifier = qualifier + " AND ";
						qualifier = qualifier + (string)fetchKeys[keypair.Split('=')[0]] +
							" like \'" + keypair.Split('=')[1] + "*\'";
					}
				}
			Console.WriteLine("Qualifier: {0}", qualifier);
			if(payload.Contains("qualifier"))
				payload["qualifier"] = qualifier;
				else payload.Add("qualifier", qualifier);
		} // End Build Company Criteria
		
		protected void BuildTaskCriteria(string[] keypairs)
		{
			string[] keypair;
			string comparator;
			
			foreach(string criterion in keypairs) 
			{
				keypair = criterion.Split('=');
				if (fetchKeys.Contains(keypair[0]))
				{
					if (((string)payload["qualifier"]).Length > 0 )
						payload["qualifier"] = (string)payload["qualifier"] + " AND ";
					if(keypair[0] == "status")
						keypair[1] = RPC2.RPCStringFromTaskStatusKey(keypair[1]);
					if (keypair[1].IndexOf('*') > -1)
						comparator = " like \'";
					 else comparator = " = \'";
					payload["qualifier"] = (string)payload["qualifier"] + 
							(string)fetchKeys[keypair[0]] + 
							comparator + keypair[1] + "\'";
				} else Console.WriteLine("key {0} discarded!", keypair[0]);
			}
			Console.WriteLine("FetchSpec = {0}", Criteria);
		} // End BuildTaskCriteria
		
		protected void BuildOldCriteria(string[] keypairs)
		{
			string[]	keypair;
			string		comparator;
			
			foreach(string criterion in keypairs) {
				keypair = criterion.Split('=');
				if (fetchKeys.Contains(keypair[0]))
				{
					if (((string)payload["qualifier"]).Length > 0 )
						payload["qualifier"] = (string)payload["qualifier"] + " AND ";
					if (keypair[1].IndexOf('*') > -1)
						comparator = " like \'";
					 else comparator = " = \'";
					//comparator = " like \'";
					payload["qualifier"] = (string)payload["qualifier"] + 
						(string)fetchKeys[keypair[0]] + 
						comparator + keypair[1] + "\'";
				} else Console.WriteLine("key {0} discarded!", keypair[0]);
			}
			Console.WriteLine("FetchSpec = {0}", Criteria);
		} // End BuildOldCriteria
		
		public bool Valid
		{
			get
			{
				if (((string)payload["qualifier"]).Length == 0)
					return false;
				if (documentType == DocumentType.Task)
					if(!(((string)payload["qualifier"]).IndexOf("type") > -1))
						return false;
				return true;
			}
		}
	}
}